#include "ObjHandler.h"

#include <stdlib.h>


#define IFIX(x, nv)													\
{																	\
	if (x > nv)														\
	{																\
		fprintf(stderr, "Index out of range: '%s'\n", str_orig);	\
		fclose(infile);												\
		return false;												\
	}																\
	if (x > 0)														\
		x = x - 1;													\
	else if (x < 0)													\
		x = nv + x;													\
}



OBJHandler::OBJHandler()
{
	// init the data counters...
	numVerts = numTris = numTexCoords = numNormals = 0 ;

	hasTexCoords = false ;
	hasNormals   = false ;

	// Init the working data...
	str[0] = 0x00 ;
	str_orig[0] = 0x00 ;
	max_words = num_words = 0 ;

	verts = 0x00 ;
	uvs = 0x00 ;
	tris = 0x00 ;
	normal = 0x00;
}

OBJHandler::~OBJHandler()
{
	//DeAllocate() ;
}

int OBJHandler::LoadOBJascii(const char *fileName)
{
	printf("Loading OBJ: %s...\n", fileName) ;

	FILE *infile = fopen(fileName, "rb");

	if (infile == NULL)
	{
		printf("ERROR cannot open file!\n") ;
		return false;
	}

	char *comment_ptr = NULL;

	int vertCount = 0, triCount = 0, texCoordCount = 0, normalCount = 0 ;

	GatherASCIIInfo(infile) ;

	while (true) 
	{
		comment_ptr = FetchLine(infile);

		if (comment_ptr == (char *) -1)  // end-of-file
			break;

		// did we get a comment?
		if (comment_ptr) 
		{
			// make_comment(comment_ptr);
			continue;
		}

		// if we get here, the line was not a comment
		int nwords = FetchWords();

		// skip empty lines
		if (nwords == 0)
			continue;

		char *first_word = words[0];

		if (EqualStrings(first_word, "v"))			// VERTICES
		{
			if (nwords < 4) 
			{
				fprintf(stderr, "Too few coordinates: '%s'", str_orig);
				fclose(infile);
				return false;
			}

			double x = atof(words[1]);
			double y = atof(words[2]);
			double z = atof(words[3]);

			verts[vertCount].x = (float)x ;
			verts[vertCount].y = (float)y ;
			verts[vertCount].z = (float)z ;

			vertCount++ ;
		}
		else if (EqualStrings(first_word, "vn"))	// NORMALS
		{
			if (nwords < 4) 
			{
				fprintf(stderr, "Too few coordinates: '%s'", str_orig);
				fclose(infile);
				return false;
			}

			double x = atof(words[1]);
			double y = atof(words[2]);
			double z = atof(words[3]);

			normalCount++ ;
		}
		else if (EqualStrings(first_word, "vt"))	// TEXTURE COORDS
		{
			if (nwords < 3) 
			{
				fprintf(stderr, "Too few coordinates: '%s'", str_orig);
				fclose(infile);
				return false;
			}

			double x = atof(words[1]);
			double y = atof(words[2]);

			uvs[texCoordCount].u = (float)x ;
			uvs[texCoordCount].v = (float)y ;

			texCoordCount++ ;
		}
		else if (EqualStrings(first_word, "f"))		// FACES
		{
			int vi0, vi1, vi2;
			int ni0, ni1, ni2;
			int ti0, ti1, ti2;

			if (nwords < 4)			// not enough to make a face!
				return false;

			GetIndices(words[1], &vi0, &ti0, &ni0);
			IFIX(vi0, vertCount); IFIX(ti0, texCoordCount); IFIX(ni0, normalCount);

			GetIndices(words[2], &vi1, &ti1, &ni1);
			IFIX(vi1, vertCount); IFIX(ti1, texCoordCount); IFIX(ni1, normalCount);

			GetIndices(words[3], &vi2, &ti2, &ni2);
			IFIX(vi2, vertCount); IFIX(ti2, texCoordCount); IFIX(ni2, normalCount);


			tris[triCount].v1 = vi0 ;
			tris[triCount].v2 = vi1 ;
			tris[triCount].v3 = vi2 ;

			tris[triCount].uv1 = ti0 ;
			tris[triCount].uv2 = ti1 ;
			tris[triCount].uv3 = ti2 ;

			// !!! Not adding normals yet !!!

			triCount++ ;
		}
		else 
		{
			printf("Do not recognize: '%s'\n", str_orig);
		}
	}
	fclose(infile);

	printf("Initial load: %d vertices %d triangles\n", numVerts, numTris) ;\


		if(this->numVerts != this->numTexCoords && this->numTexCoords != 0)
		{
			puts("fixing texture problem: match the number of vertex and texcoord.");
			printf("vertex %d text %d faces %d\n", this->numVerts, this->numTexCoords, this->numTris);

			OBJtexCoord* texArray = (OBJtexCoord*)malloc(this->numVerts*sizeof(OBJtexCoord));
			int* texNum = (int*)malloc(this->numVerts*sizeof(int));
			for(int i=0; i<this->numVerts; i++){
				texArray[i].u = texArray[i].v = 0.0;
				texNum[i] = 0;
			}
			for(int i=0; i<this->numTris; i++)
			{
				int indexVertex = this->tris[i].v1;
				int indexUV = this->tris[i].uv1;
				//printf("%d %d", indexVertex, indexUV); getchar();
				texArray[ indexVertex ].u += this->uvs[ indexUV ].u;
				texArray[ indexVertex ].v += this->uvs[ indexUV ].v;
				texNum[ indexVertex ]++;
				
				indexVertex = this->tris[i].v2;
				indexUV = this->tris[i].uv2;
				texArray[ indexVertex ].u += this->uvs[ indexUV ].u;
				texArray[ indexVertex ].v += this->uvs[ indexUV ].v;
				texNum[ indexVertex ]++;
				
				indexVertex = this->tris[i].v3;
				indexUV = this->tris[i].uv3;
				texArray[ indexVertex ].u += this->uvs[ indexUV ].u;
				texArray[ indexVertex ].v += this->uvs[ indexUV ].v;
				texNum[ indexVertex ]++;

				this->tris[i].uv1 = this->tris[i].v1;
				this->tris[i].uv2 = this->tris[i].v2;
				this->tris[i].uv3 = this->tris[i].v3;
			}
			for(int i=0; i<this->numTexCoords; i++) {
				this->uvs[i].u = this->uvs[i].v = 0.0;
			}
			for(int i=0; i<this->numVerts; i++){
				if(texNum[i] == 0 ) {printf("index %d", i); getchar(); 
				continue;}
				this->uvs[i].u = texArray[i].u / float(texNum[i]);
				this->uvs[i].v = texArray[i].v / float(texNum[i]);
			}

			this->numTexCoords = this->numVerts;
			puts("texture done.");
		}

	puts("find normals");
	this->numNormals = this->numVerts;
	for( int i=0; i<this->numTris; i++) {
		// 1
		int index1 = tris[i].v1;
		int index2 = tris[i].v2;
		int index3 = tris[i].v3;
		OBJvertex v1, v2;
		v1 = verts[index2] - verts[index1]; 
		v2 = verts[index3] - verts[index1]; 
		OBJvertex v3;
		v3 = OBJvertex::Cross(v1, v2);
		v3.normalize();
		this->normal[index1] += v3;
		this->normal[index2] += v3;
		this->normal[index3] += v3;
		
	}
	for(int i=0; i<numNormals; i++ ){
		normal[i].normalize();
		//printf("%f %f %f\n", normal[i].x, normal[i].y, normal[i].z);getchar();

	}
	puts("normal done!");

	if(this->numTexCoords > 1) {
		puts("find tangent");
		
		OBJvertex *tan1 = new OBJvertex[numVerts]; // = (*OBJvertex)malloc(this->numVerts*sizeof(OBJvertex));
		OBJvertex *tan2 = new OBJvertex[numVerts]; // = (*OBJvertex)malloc(this->numVerts*sizeof(OBJvertex));

		for( int i=0; i<this->numTris; i++) {


			int index1 = tris[i].v1;
			int index2 = tris[i].v2;
			int index3 = tris[i].v3;
			
			float x1 = verts[index2].x - verts[index1].x; 
			float y1 = verts[index2].y - verts[index1].y; 
			float z1 = verts[index2].z - verts[index1].z;
			
			float x2 = verts[index3].x - verts[index1].x; 
			float y2 = verts[index3].y - verts[index1].y; 
			float z2 = verts[index3].z - verts[index1].z;
			
			
			float s1 = uvs[index2].u - uvs[index1].u;
			float t1 = uvs[index2].v - uvs[index1].v;

			float s2 = uvs[index3].u - uvs[index1].u;
			float t2 = uvs[index3].v - uvs[index1].v;

			float r = 1.0 / (s1 * t2 - s2 * t1);
			OBJvertex sdir;
			sdir.x = (t2 * x1 - t1 * x2) * r;
			sdir.y = (t2 * y1 - t1 * y2) * r;
			sdir.z = (t2 * z1 - t1 * z2) * r;
			OBJvertex tdir;
			tdir.x = (s1 * x2 - s2 * x1) * r; 
			tdir.y = (s1 * y2 - s2 * y1) * r;
			tdir.z = (s1 * z2 - s2 * z1) * r;

			tan1[index1] += sdir;
			tan1[index2] += sdir;
			tan1[index3] += sdir;
			tan2[index1] += tdir;
			tan2[index2] += tdir;
			tan2[index3] += tdir;

		}
		for(int i=0; i< this->numVerts; i++){
			OBJvertex n = this->normal[i];
			OBJvertex t = tan1[i];
			this->tangent[i] = (t - n * OBJvertex::Dot(n,t));
			//this->tangent[i] = t;
			this->tangent[i].normalize();
			//this->binormal[i] = tan2[i];
			//this->binormal[i].normalize();
			bool handness = OBJvertex::Dot(OBJvertex::Cross(n,t), tan2[i])<0.0;
			if(handness) {
				this->tangent[i] = this->tangent[i] * (-1.0);
				//this->binormal[i] = this->binormal[i] * (-1.0);
			}
		}

	}
	puts("tangent done.");

	puts("find binormal");
	for(int i=0; i< this->numVerts; i++){
			OBJvertex n = this->normal[i];
			OBJvertex t = this->tangent[i];
			this->binormal[i] = OBJvertex::Cross(n,t); 
	}
	puts("binormal done");

	invTBNMatrix();

	//this->SaveOBJascii("good.obj");

/*
	if (hasNormals)
	{
		// Now we need to figure out how to associate a normal with each index

		array1d<int> nextv;			// 'next' vertex that is the same position (alias) as this vertex but with a diff. normal
		nextv.Allocate(v.size);	nextv.count = v.count;
		nextv.Clear(-1);

		for (int i = 0; i < f.count; i++)			// for each face (triangle)
		{
			for (int x = 0; x < 3; x++)				// for each of the three corners of this triangle
			{
				DWORD vi = f[i][x];							// we want to associate the vertex position of this vertex index,
				DWORD ni = fn[i][x];						// with the normal of this normal index

				while (true)
				{
					if (v[vi].normal.x == FLT_MAX)			// if no normal associated with this vertex
					{
						v[vi].normal = n[ni];				// copy the normal to the vertex, and use this vertex
						break;
					}
					else if (v[vi].normal == n[ni])			// otherwise, if the vertex normal is the same as our normal
					{
						break;								// just use this vertex
					}
					
					int nvi = nextv[vi];
					if (nvi == -1)							// otherwise, if the next vertex alias is undefined
					{
						vi = nextv[vi] = v.Add(v[vi]);		// create a new vertex alias, (copy of this vetex)
						v[vi].normal = n[ni];				// except it has our normal
						nextv.Add(-1);						// (make sure to allocate space in the nextv alias list too)
						break;								// and use that one
					}
					vi = nvi;								// otherwise, go to the next vertex in the alias list
				}
				f[i][x] = vi;
			}
		}
	}
*/

	printf("Done!\n");

	return true;
}
void OBJHandler::invTBNMatrix(){
	puts("find inverse TBN matrix");

	this->TBN = new OBJmatrix[this->numVerts];
	
	for(int i=0; i< this->numVerts; i++){
		OBJvertex n = this->normal[i];
		OBJvertex t = this->tangent[i];
		OBJvertex b = this->binormal[i];

		float det = t.x*b.y*n.z - t.z*b.y*n.x + b.x*n.y*t.z - b.z*n.y*t.x + n.x*t.y*b.z - n.z*t.y*b.x;
		det = 1.0/det;
		OBJvertex bn = OBJvertex::Cross(b, n);
		OBJvertex nt = OBJvertex::Cross(n, t);
		OBJvertex tb = OBJvertex::Cross(t, b);
		float m[3][3] = {{bn.x * det, - bn.y * det, bn.z * det}, 
		{-nt.x *det, nt.y *det, -nt.z *det}, {tb.x *det, -tb.y *det, tb.z *det}};
		OBJmatrix matrix(m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2]);

		TBN[i] = matrix;

	}
	
	
	
	puts("TBN done.");


}
int OBJHandler::LoadOBJbin(const char *fileName)
{
	int i ;

	FILE *infile = fopen(fileName, "rb") ;

	if (!infile)
	{
		printf ("File %s could not be opened for writing.\n", fileName) ;
		return false ;
	}

	printf("Loading binaryOBJ %s...\n", fileName) ;

	// Has Flags
	fread(&hasTexCoords, sizeof(unsigned char), 1, infile) ;
	fread(&hasNormals,   sizeof(unsigned char), 1, infile) ;

	// Numbers
	fread(&numVerts, sizeof(int), 1, infile) ;

	if (hasTexCoords)
		fread(&numTexCoords, sizeof(int), 1, infile) ;

	fread(&numTris, sizeof(int), 1, infile) ;

	Allocate() ;

	// Data
	for (i = 0; i < numVerts; i++)
	{
		fread(&(verts[i].x), sizeof(float), 1, infile) ;
		fread(&(verts[i].y), sizeof(float), 1, infile) ;
		fread(&(verts[i].z), sizeof(float), 1, infile) ;
	}

	if (hasTexCoords)
	{
		for (i = 0; i < numTexCoords; i++)
		{
			fread(&(uvs[i].u), sizeof(float), 1, infile) ;
			fread(&(uvs[i].v), sizeof(float), 1, infile) ;
		}
	}

	for (i = 0; i < numTris; i++)
	{
		fread(&(tris[i].v1), sizeof(int), 1, infile) ;
		fread(&(tris[i].v2), sizeof(int), 1, infile) ;
		fread(&(tris[i].v3), sizeof(int), 1, infile) ;

		if (hasTexCoords)
		{
			fread(&(tris[i].uv1), sizeof(int), 1, infile) ;
			fread(&(tris[i].uv2), sizeof(int), 1, infile) ;
			fread(&(tris[i].uv3), sizeof(int), 1, infile) ;
		}
	}
	

	fclose(infile) ;

	return true ;
}

int OBJHandler::SaveOBJascii(const char *fileName)
{
	FILE *outfile = fopen(fileName, "wb") ;

	if (!outfile)
	{
		printf ("File %s could not be opened for writing.\n", fileName) ;
		return false ;
	}

	printf("Saving OBJ %s...\n", fileName) ;

	int i = 0 ;

	// Verts
	for (i = 0; i < numVerts; i++)
	{
		fprintf(outfile, "v %.4f %.4f %.4f\n", verts[i].x, verts[i].y, verts[i].z) ;
	}

	// Texture coordinates (if needed)...
	if (hasTexCoords)
	{
		for (i = 0; i < numTexCoords; i++)
		{
			fprintf(outfile, "vt %f %f\n", uvs[i].u, uvs[i].v) ;
		}
	}

	//Triangles
	for (i = 0; i < numTris; i++)
	{
		if (hasTexCoords)
			fprintf(outfile, "f %d/%d %d/%d %d/%d\n", tris[i].v1+1, tris[i].uv1+1, tris[i].v2+1, tris[i].uv2+1, tris[i].v3+1, tris[i].uv3+1) ;
		else
			fprintf(outfile, "f %d %d %d\n", tris[i].v1+1, tris[i].v2+1, tris[i].v3+1) ;
	}

	fclose(outfile) ;

	printf("Done.\n") ;

	return true ;
}

int OBJHandler::SaveOBJbin(const char *fileName)
{
	int i ;

	FILE *outfile = fopen(fileName, "wb") ;

	if (!outfile)
	{
		printf ("File %s could not be opened for writing.\n", fileName) ;
		return false ;
	}

	printf("Saving binaryOBJ %s...\n", fileName) ;

	// First, tell if there are any texture coordinates or normals
	fwrite(&hasTexCoords, sizeof(unsigned char), 1, outfile) ;
	fwrite(&hasNormals, sizeof(unsigned char), 1, outfile) ;

	// Now write out the numbers that are necessary...
	fwrite(&numVerts, sizeof(int), 1, outfile) ;
	
	if (hasTexCoords)
		fwrite(&numTexCoords, sizeof(int), 1, outfile) ;
	
	fwrite(&numTris, sizeof(int), 1, outfile) ;

//	if (hasNormals)
//		fwrite(&numNormals, sizeof(int), 1, outfile) ;


	// BINARY OBJ'S ARE 0-INDEXED !


	// Now write out the vertex data, interleved XYZ...
	for (i = 0; i < numVerts; i++)
	{
		fwrite(&(verts[i].x), sizeof(float), 1, outfile) ;
		fwrite(&(verts[i].y), sizeof(float), 1, outfile) ;
		fwrite(&(verts[i].z), sizeof(float), 1, outfile) ;
	}

	// Now write out the texture coordinate data, interleved UV...
	if (hasTexCoords)
	{
		for (i = 0; i < numTexCoords; i++)
		{
			fwrite(&(uvs[i].u), sizeof(float), 1, outfile) ;
			fwrite(&(uvs[i].v), sizeof(float), 1, outfile) ;
		}
	}

	// Now write out the vertex data, interleved XYZ...
	for (i = 0; i < numTris; i++)
	{
		fwrite(&(tris[i].v1), sizeof(int), 1, outfile) ;
		fwrite(&(tris[i].v2), sizeof(int), 1, outfile) ;
		fwrite(&(tris[i].v3), sizeof(int), 1, outfile) ;

		if (hasTexCoords)
		{
			fwrite(&(tris[i].uv1), sizeof(int), 1, outfile) ;
			fwrite(&(tris[i].uv2), sizeof(int), 1, outfile) ;
			fwrite(&(tris[i].uv3), sizeof(int), 1, outfile) ;
		}
	}

	fclose(outfile) ;

	return true ;
}

void OBJHandler::GatherASCIIInfo(FILE *fp)
{
	int currentLocation = ftell(fp) ;

	// Rewind to the beginning of the file...
	fseek(fp, 0, SEEK_SET) ;
	
	char tempString[BIG_STRING] ;
	
	while (!feof(fp))
	{
		tempString[0] = 0x00 ;
		fgets(tempString, BIG_STRING, fp) ;
		if ((tempString[0] == 'v' || tempString[0] == 'V') && tempString[1] == ' ')
		{
			numVerts++ ;
		}
		else if ((tempString[0] == 'f' || tempString[0] == 'F') && tempString[1] == ' ')
		{
			numTris++ ;
		}
		else if (((tempString[0] == 'v' && tempString[1] == 't') || 
				  (tempString[0] == 'v' && tempString[1] == 't') ||
				  (tempString[0] == 'V' && tempString[1] == 't') ||
				  (tempString[0] == 'V' && tempString[1] == 'T'))
				   && tempString[2] == ' ')
		{
			numTexCoords++ ;
			hasTexCoords = true ;
		}
	}

	// Sanity checks...
	if (hasTexCoords)
		if (numTexCoords != numVerts)
			printf("Uh, dude, you have dissimilar texture coords and vertices\n") ;

	// Allocate the proppa' data...
	Allocate() ;

	// Seek back to where you were before in the file...
	fseek(fp, currentLocation, SEEK_SET) ;
}

char *OBJHandler::FetchLine(FILE *fp)
{
	// read in a line
	char *ptr;
	char *ptr2;
	char *result = fgets(str, BIG_STRING, fp);

	// return NULL if we're at the end-of-file
	if (result == NULL)
		return ((char *) -1);

	// convert line-feed and tabs into spaces
	// (this guarentees that there will be a space before the
	//  null character at the end of the string)

	str[BIG_STRING-2] = ' ';
	str[BIG_STRING-1] = '\0';

	for (ptr = str; *ptr != '\0'; ptr++) 
	{
		if (*ptr == '\t') 
			*ptr = ' ';
		else if (*ptr == '\n') 
		{
			*ptr = ' ';
			break;
		}
	}

	// copy the line
	for (ptr = str, ptr2 = str_orig; *ptr != '\0'; ptr++, ptr2++)
		*ptr2 = *ptr;

	*ptr2 = '\0';

	// look to see if this is a comment line (first non-space is '#')
	for (ptr = str; *ptr != '\0'; ptr++) 
	{
		if (*ptr == '#') 
		{
			ptr++;
			while (*ptr == ' ')
				ptr++;
			return (ptr);
		}
		else if (*ptr != ' ') 
			break;
	}

	// if we get here, we've got a non-comment line
	// strip off trailing comments

	while (*ptr != '\0') 
	{
		if (*ptr == '#') 
		{
			*ptr++ = ' ';
			*ptr = '\0';
			break;
		}
		ptr++;
	}

	return (NULL);
}

int OBJHandler::FetchWords(void)
{
	char *ptr;

	// allocate room for words if necessary
	if (max_words == 0) 
	{
		max_words = 20;
		words = (char **) malloc (sizeof (char *) * max_words);
	}

	// find the words in the line

	ptr = str;
	num_words = 0;

	while (*ptr != '\0') 
	{
		// jump over leading spaces */
		while (*ptr == ' ')
			ptr++;

		// break if we reach the end
		if (*ptr == '\0')
			break;

		// allocate more room for words if necessary
		if (num_words >= max_words) 
		{
			max_words += 10;
			words = (char **) realloc (words, sizeof (char *) * max_words);
		}

		// save pointer to beginning of word
		words[num_words++] = ptr;

		// jump over non-spaces
		while (*ptr != ' ')
			ptr++;

		// place a null character here to mark the end of the word
		*ptr++ = '\0';
	}

	// return the number of words */
	return (num_words);
}

/******************************************************************************
Compare two strings.  Returns 1 if they are the same, 0 if not.
******************************************************************************/
int OBJHandler::EqualStrings(char *s1, char *s2)
{
	while (*s1 && *s2)
		if (*s1++ != *s2++)
			return (0);

	if (*s1 != *s2)
		return (0);
	else
		return (1);
}



void OBJHandler::GetIndices(char *word, int *vindex, int *tindex, int *nindex)
{
	char *null = " ";
	char *ptr;
	char *tp;
	char *np;

	// by default, the texture and normal pointers are set to the null string
	tp = null;
	np = null;

	// replace slashes with null characters and cause tp and np to point
	// to character immediately following the first or second slash
	for (ptr = word; *ptr != '\0'; ptr++) 
	{
		if (*ptr == '/') 
		{
			if (tp == null)
				tp = ptr + 1;
			else
				np = ptr + 1;
			*ptr = '\0';
		}
	}
	*vindex = atoi (word);
	*tindex = atoi (tp);
	*nindex = atoi (np);
}

void OBJHandler::Allocate(void)
{
	verts = new OBJvertex[numVerts] ;
	tris  = new OBJtriangle[numTris] ;
	normal = new OBJvertex[numVerts];
	tangent = new OBJvertex[numVerts];
	binormal = new OBJvertex[numVerts];

	if (hasTexCoords)
		uvs = new OBJtexCoord[numTexCoords] ;

//	if (hasNormals)
}

void OBJHandler::DeAllocate(void)
{	
	if (verts)
		delete[] verts ;
	verts = 0x00 ;

	if (uvs)
		delete[] uvs ;
	uvs = 0x00 ;

	if (tris)
		delete[] tris ;
	tris = 0x00 ;
}